#include "crane_keypadc.h"

#if USE_CRANE_KEYPADC

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "../common/utils.h"

#define MANUAL        0
#define AUTO          1

#define SINGLE_KEY    0
#define MULTI_KEY     1

#define KEDPAD_DEBOUNCE_TIMERINTERVAL    20         /* 20 ms */

#define KEYPAD_ROW_NUM        (0x5)
#define KEYPAD_COL_NUM        (0x5)

#define KEYPAD_COL_NUM_MAX    (0x8)

#define KEYPAD_COL_ROW_NUM    (KEYPAD_ROW_NUM > KEYPAD_COL_NUM ? KEYPAD_ROW_NUM : KEYPAD_COL_NUM)

#define GET_COLUMN_IDENTIFIES(value, x)    ((value >> (x % 2 ? 16 : 0)) & 0xFF)

#define APBC_BASE                   0xD4015000
#define APBC_KPC_CLK_RST            (APBC_BASE + 0x0030)    /* 32 bit    APBClock Clock/Reset Control*/
#define APBC_KPC_CLK_RST_RST        BIT_2    /* Keypad Controller Reset Generation */
#define APBC_KPC_CLK_RST_FNCLK      BIT_1    /* Keypad Controller Functional Clock Enable/Disable */
#define APBC_KPC_CLK_RST_APBCLK     BIT_0    /* Keypad Controller APB Bus Clock Enable/Disable */

#define KPC_BASE      0xD4012000
#define KPC_ASMKP0    (KPC_BASE + 0x0028)    /* 32 bit Keypad Automatic Scan Multiple Keypress Reg 0 */
#define KPC_ASMKP1    (KPC_BASE + 0x0030)    /* 32 bit Keypad Automatic Scan Multiple Keypress Reg 1 */
#define KPC_ASMKP2    (KPC_BASE + 0x0038)    /* 32 bit Keypad Automatic Scan Multiple Keypress Reg 2 */
#define KPC_ASMKP3    (KPC_BASE + 0x0040)    /* 32 bit Keypad Automatic Scan Multiple Keypress Reg 3 */

/* KPC_PC        0x0000    KPC Keypad Control Register */
#define KPC_PC_ASACT                BIT_29            /* Automatic Scan on Activity */
#define KPC_PC_MKRN                 (BIT_26 | BIT_27 | BIT_28)        /* Matrix Keypad Row Number */
#define KPC_PC_MKCN                 (BIT_23 | BIT_24 | BIT_25)        /* Keypad Column Number */
#define KPC_PC_MI                   BIT_22            /* Matrix Keypad Interrupt */
#define KPC_PC_IMKP                 BIT_21            /* Ignore Multiple Keypress */
#define KPC_PC_MS7                  BIT_20            /* Manual Matrix Scan line 7 */
#define KPC_PC_MS6                  BIT_19            /* Manual Matrix Scan line 6 */
#define KPC_PC_MS5                  BIT_18            /* Manual Matrix Scan line 5 */
#define KPC_PC_MS4                  BIT_17            /* Manual Matrix Scan line 4 */
#define KPC_PC_MS3                  BIT_16            /* Manual Matrix Scan line 3 */
#define KPC_PC_MS2                  BIT_15            /* Manual Matrix Scan line 2 */
#define KPC_PC_MS1                  BIT_14            /* Manual Matrix Scan line 1 */
#define KPC_PC_MS0                  BIT_13            /* Manual Matrix Scan line 0 */
#define KPC_PC_ME                   BIT_12            /* Matrix Keypad Enable */
#define KPC_PC_MIE                  BIT_11            /* Matrix Interrupt Enable */
#define KPC_PC_DN_MSK               (BIT_6 | BIT_7 | BIT_8)        /* Direct Key Number */
#define KPC_PC_REE1                 BIT_3            /* Rotary Encoder 1 Enable */
#define KPC_PC_REE0                 BIT_2            /* Rotary Encoder 0 Enable */
#define KPC_PC_DE                   BIT_1            /* Direct Keypad Enable */
#define KPC_PC_DIE                  BIT_0            /* Direct Keypad Interrupt Enable */

/* KPC_AS        0x0020    KPC Keypad Automatic Scan Register */
#define KPC_AS_SO                   BIT_31                /* Scan On */
#define KPC_AS_MUKP_MSK             (BIT_26 | BIT_27 | BIT_28 | BIT_29 | BIT_30)        /* Multiple Keys Pressed */
#define KPC_AS_MUKP_BASE            26
#define KPC_AS_RP_MSK               (BIT_4 | BIT_5 | BIT_6 | BIT_7)        /* Row Pressed */
#define KPC_AS_RP_BASE              4
#define KPC_AS_CP_MSK               (BIT_0 | BIT_1 | BIT_2 | BIT_3)        /* Column Pressed */
#define KPC_AS_CP_BASE              0

#define PMU_SD_ROT_WAKE_CLR         (0x07c)
#define KB_WK_CLR                   BIT_3

#define INTERRUPT_KEYPAD            (9)

enum {
    KEY_TYPE_DIRECT = 1,
    KEY_TYPE_MATRIX,
    KEY_TYPE_ROTARY_0,
    KEY_TYPE_ROTARY_1
};

struct kpc_regs_s {
    uint32_t kpc_pc;
    uint32_t reserved0;
    uint32_t kpc_dk;
    uint32_t reserved1;
    uint32_t kpc_rec;
    uint32_t reserved2;
    uint32_t kpc_mk;
    uint32_t reserved3;
    uint32_t kpc_as;
    uint32_t reserved4;
    uint32_t kpc_asmkp0;
    uint32_t reserved5;
    uint32_t kpc_asmkp1;
    uint32_t reserved6;
    uint32_t kpc_asmkp2;
    uint32_t reserved7;
    uint32_t kpc_asmkp3;
    uint32_t reserved8;
    uint32_t kpc_kdi;
};

const uint8_t keypad_matrix_evb[KEYPAD_ROW_NUM][KEYPAD_COL_NUM] =
{
    { ' ', KP_UP, KP_PWR, KP_3, KP_8 },
    { 'c', KP_SL, KP_DW, KP_4, KP_0 },
    { KP_VD, KP_LT, KP_CALL, KP_5, KP_POUND },
    { KP_VU, KP_OK, KP_1, KP_6, KP_9 },
    { KP_SR, KP_RT, KP_2, KP_7, KP_STAR },
};

const uint32_t matrix_keypress_reg[4] = { KPC_ASMKP0, KPC_ASMKP1, KPC_ASMKP2, KPC_ASMKP3 };

static volatile struct kpc_regs_s *kpc_reg = (struct kpc_regs_s *)KPC_BASE;

static uint32_t pressed_key[KEYPAD_COL_NUM_MAX] = { 0, 0, 0, 0, 0 };
static uint32_t pressed_key_bak[KEYPAD_COL_NUM_MAX] = { 0, 0, 0, 0, 0 };

static keypad_handler_t kp_handler = NULL;

static void* keypad_hisr_ref = NULL;

#define REG32(addr) ((volatile uint32_t *)(addr))
#define writel(v, a) (*REG32(a) = (v))
#define readl(a) (*REG32(a))
#define PMU_BASE_ADDR 0xd4282800
static void pmu_write(uint32_t offset, uint32_t val)
{
    writel(val, PMU_BASE_ADDR + offset);
}

static uint32_t pmu_read(uint32_t offset)
{
    return (uint32_t)readl(PMU_BASE_ADDR + offset);
}

static void keypad_init_clk(void)
{
    writel(APBC_KPC_CLK_RST_FNCLK | APBC_KPC_CLK_RST_APBCLK, APBC_KPC_CLK_RST);
}

static void keypad_set_debounce(int msecond) /* in msecond for both direct/matrix key */
{
    kpc_reg->kpc_kdi = ((msecond << 8) | msecond);
}

static void keypad_enable(uint32_t key_type)
{
    uint32_t value = 0;

    switch (key_type) {
        case KEY_TYPE_MATRIX:
            value =  KPC_PC_ME | KPC_PC_MIE;
            break;
        case KEY_TYPE_DIRECT:
            value = KPC_PC_DE | KPC_PC_DIE;
            break;
        case KEY_TYPE_ROTARY_0:
            value = KPC_PC_REE0;
            break;
        case KEY_TYPE_ROTARY_1:
            value = KPC_PC_REE1;
        default: break;
    }
    kpc_reg->kpc_pc |= value;
}

uint32_t keypad_is_auto_mode(void)
{
    if (kpc_reg->kpc_pc & KPC_PC_ASACT) {
        return 1;
    }
    else {
        return 0;
    }
}

void keypad_clear_wakup_interrupt(void)
{
    uint32_t value = pmu_read(PMU_SD_ROT_WAKE_CLR);

    value |= KB_WK_CLR;
    pmu_write(PMU_SD_ROT_WAKE_CLR, value);
}

static void keypad_hisr(void)
{
    uint32_t key_pressed_num = 0, pressed_col = 0, pressed_row = 0, i = 0;
    uint32_t key_pressed0;
    uint32_t timeout_cnt = 0;
    uint8_t key_val;
    /*clean inter status*/
    if (!keypad_is_auto_mode()) {
        return;
    }

    memcpy(&pressed_key_bak, &pressed_key, sizeof(pressed_key));
    memset(&pressed_key, 0, sizeof(pressed_key));

    /* single key; auto mode */
    /* wait for KPC_AS_SO off for valid data */
    while (kpc_reg->kpc_as & KPC_AS_SO) {
        if (timeout_cnt++ > 0xfffff) {
            //printf("keypad_isr : timeout Error -- scan on with interrupt!\r\n");
            return;
        }
    }

    key_pressed0 = kpc_reg->kpc_as;
    key_pressed_num = (key_pressed0 & KPC_AS_MUKP_MSK) >> KPC_AS_MUKP_BASE;
    if (key_pressed_num == 1) { /* only one key */
        pressed_col = (key_pressed0 & KPC_AS_CP_MSK) >> KPC_AS_CP_BASE;
        pressed_row = (key_pressed0 & KPC_AS_RP_MSK) >> KPC_AS_RP_BASE;
        if (pressed_col != 0xf && pressed_row != 0xf) {
            pressed_key[pressed_col] |= (1 << pressed_row);
        }
    }
    else if (key_pressed_num > 1) {  /* more than one key */
        for (i = 0; i < (KEYPAD_COL_ROW_NUM + 1) / 2; i++) {
            timeout_cnt = 0;
            /* if scan-on, the data is invalid */
            while (readl(matrix_keypress_reg[i]) & KPC_AS_SO) {
                if (timeout_cnt++ > 0xfffff) {
                    // printf("timeout Error -- scan on with interrupt!\r\n");
                    return;
                }
            }
            key_pressed0 = readl(matrix_keypress_reg[i]);
            pressed_key[2 * i] = GET_COLUMN_IDENTIFIES(key_pressed0, 2 * i);
            pressed_key[2 * i + 1] = GET_COLUMN_IDENTIFIES(key_pressed0, 2 * i + 1);
        }
    }

    for (pressed_col = 0; pressed_col < KEYPAD_COL_NUM; pressed_col++) {
        if (!pressed_key_bak[pressed_col] && !pressed_key[pressed_col]) {
            continue;
        }
        for (pressed_row = 0; pressed_row < KEYPAD_ROW_NUM; pressed_row++) {
            key_val = keypad_matrix_evb[pressed_row][pressed_col];
            if ((pressed_key_bak[pressed_col] & BIT(pressed_row)) && (pressed_key[pressed_col] & BIT(pressed_row))) {
                /* Do nothing ? */
            }
            else if ((pressed_key_bak[pressed_col] & BIT(pressed_row)) && !(pressed_key[pressed_col] & BIT(pressed_row))) {
                // printf("\r\n*k:%c-%c*\r\n",key_val,'U');
                if (kp_handler != NULL) {
                    kp_handler(KEY_UP, key_val);
                }
            }
            else if (!(pressed_key_bak[pressed_col] & BIT(pressed_row)) && (pressed_key[pressed_col] & BIT(pressed_row))) {
                // printf("\r\n*k:%c-%c*\r\n",key_val,'D');
                if (kp_handler != NULL) {
                    kp_handler(KEY_DOWN, key_val);
                }
            }
        }
    }
}

static void keypad_wake_isr(void)
{
    keypad_clear_wakup_interrupt();
    OS_Activate_HISR(&keypad_hisr_ref);
}

static void keypad_config(uint32_t is_auto, uint32_t is_mk, uint32_t debounce, uint32_t key_type)
{
    uint32_t value;

    /* keypad mfp should be configed in board init */
    /* reset and enbale APB KPD clock */
    keypad_init_clk();

    /* Clear the auto scan bit */
    /* set/reset auto-scan-on-activity bit according to is_auto */
    value = KPC_PC_MKRN | KPC_PC_MKCN | KPC_PC_MS0 | KPC_PC_MS1 |    \
            KPC_PC_MS2 | KPC_PC_MS3 | KPC_PC_MS4 | KPC_PC_MS5 | KPC_PC_MS6 | \
            KPC_PC_MS7 | KPC_PC_DN_MSK;
    if (is_auto) {
        value |= KPC_PC_ASACT;
    }
    else {
        value &= ~KPC_PC_ASACT;
    }
    /* set/reset multi-key according to is_mk */
    if (is_mk) {
        value &= ~KPC_PC_IMKP;
    }
    else {
        value |= KPC_PC_IMKP;
    }
    kpc_reg->kpc_pc =  value;

    keypad_set_debounce(debounce);
    keypad_enable(key_type);
    register_int_handler(INTERRUPT_KEYPAD_WAKE, (int_handler)keypad_wake_isr, NULL);
    unmask_interrupt(INTERRUPT_KEYPAD_WAKE);
}

void crane_keypadc_set_handler(keypad_handler_t handler)
{
    kp_handler = handler;
}

void crane_keypadc_init(void)
{
    OS_Create_HISR(&keypad_hisr_ref, "uiKpdIsr", keypad_hisr, HISR_PRIORITY_2);
    keypad_config(AUTO, MULTI_KEY, KEDPAD_DEBOUNCE_TIMERINTERVAL, KEY_TYPE_MATRIX);
}

#endif /* USE_CRANE_KEYPADC */
